import collections
import numpy as np

# We need to transform our coordinates from the millimeter-based coordinate
# system (X,Y,Z) they're expressed in, to the voxel-address-based coordinate
# system (I,R,C) used to take array slices from our CT scan data.
#
# The patient coordinate system defines positive X to be patient-left (left),
# positive Y to be patient-behind (posterior), and positive Z to be
# toward-patient-head (superior). Left-posterior-superior is sometimes
# abbreviated LPS.
#
# When dealing with CT scans, we refer to the array dimensions as index, row,
# and column.
#
# Typically, the size of the voxels in CT is 1.125mm x 1.125 mm x 2.5 mm or
# similar. When plotted using square pixels, the non-cubic voxels can end up
# looking somewhat distorted, similar to the distortion near the north and
# south poles when using a Mercator projection map. We will need to apply a
# scaling factor if we want the images to depict realistic proportions.
#
# CTs are commonly 512 rows by 512 columns, with the index dimension ranging
# from around 100 total slices up to perhaps 250 slices (250 slices times 2.5
# millimeters is typically enough to contain the anatomical region of
# interest). This results in a lower bound of approximately 2**25 voxels, or
# about 32 million data points. Each CT specifies the voxel size in millimeters
# as part of the file metadata; for example, we'll call ct_mhd.GetSpacing().
#
# To go from voxel indices to coordinates, we need to follow these four steps
# in order:
#   - Flip the coordinates from IRC to CRI, to align with XYZ.
#   - Scale the indices with the voxel sizes.
#   - Matrix-multiply with the directions matrix, using @ in Python.
#   - Add the offset for the origin.
# To go back from XYZ to IRC, we need to perform the inverse of each step in
# the reverse order.


XyzTuple = collections.namedtuple("XyzTuple", ["x", "y", "z"])
IrcTuple = collections.namedtuple("IrcTuple", ["index", "row", "col"])


def xyz2irc(coord_xyz, origin_xyz, vxSize_xyz, direction_a):
    """
    Using the transformation information to convert a nodule center coordinate
    in patient coordinates (X,Y,Z) to an array index (Index,Row,Column)
    """
    origin_a = np.array(origin_xyz)
    vxSize_a = np.array(vxSize_xyz)
    coord_a = np.array(coord_xyz)
    cri_a = ((coord_a - origin_a) @ np.linalg.inv(direction_a)) / vxSize_a
    cri_a = np.round(cri_a)
    return IrcTuple(int(cri_a[2]), int(cri_a[1]), int(cri_a[0]))


def irc2xyz(coord_irc, origin_xyz, vxSize_xyz, direction_a):
    """
    Using the transformation information to convert a nodule center coordinate
    in an array index (Index,Row,Column) to patient coordinates (X,Y,Z)
    """
    cri_a = np.array(coord_irc)[::-1]
    origin_a = np.array(origin_xyz)
    vxSize_a = np.array(vxSize_xyz)
    coords_xyz = (direction_a @ (cri_a * vxSize_a)) + origin_a
    # coords_xyz = (direction_a @ (idx * vxSize_a)) + origin_a
    return XyzTuple(*coords_xyz)
